from functools import wraps
import inspect
import sys, traceback
import os
import subprocess

def errorMsg(msg):
    return "\033[41;30;1m*{}*\033[0m".format(msg)
def heading(msg):
    return "\033[45;37;1;4m{}\033[0m".format(msg)

PASSED = "\033[42;1mPASSED\033[0m"
FAILED = "\033[41;1mFAILED\033[0m"

class AssertionResult(object):
    """
    Contains result of assertion.
    @passed: bool
    @feedback: str, Explanation of what was wrong
    @actual: actual value of object being tested
    @targe: targe value of test
    Created by Test.assert* methods.
    """
    def __init__(self, passed=True, feedback="", actual=None, target=None):
        self.passed = passed
        self.feedback = feedback
        self.actual = actual
        self.target = target

class TestResult(object):
    """
    Contains detailed results of test.
    Created by @appendToResults decorator function.
    Stored in Test.results.
    @test_name
    @test_doc
    @passed
    @feedback
    @actual
    @target
    """
    def __init__(self, test_name, test_doc, assertion_result):
        self.test_name = test_name
        self.test_doc = test_doc
        self.passed = assertion_result.passed
        self.feedback = assertion_result.feedback
        self.actual = assertion_result.actual
        self.target = assertion_result.target
    def __str__(self):
        string = """
================================================================================
Test Function Name: \033[45;1m{}\033[0m
Test Requirement: \033[33m{}\033[0m

\ttarget value: {}
\tactual value: {}

Result: {}
{}
"""
        msg = FAILED
        if self.passed == True:
            msg = PASSED
            
        return string.format(self.test_name, self.test_doc, self.target, self.actual,msg, "Feedback: %s" % self.feedback)

def appendToResults(f):
    """
    Decorator function
    Appends results of Test.assert* functions to Test.results
    """
    @wraps(f)
    def decorated(self, *args, **kwargs):
        self.results.append(TestResult(f.__name__, f.__doc__, f(self, *args, **kwargs)))
    return decorated

class Test(object):
    tests = []
    def __init__(self, module_name):
        self.module_name = module_name
        self.tests = [getattr(self, t) for t in dir(self) if t.startswith("test_")]
        self.results = []
        Test.tests.append(self)

    @staticmethod
    def run_all():
        for test in Test.tests:
            print heading("REQUIREMENTS")
            print test.requirements
            raw_input("Hit 'Enter' to continue...")
            print heading("RUNNING THE TESTS")
            test.run()
            raw_input("Hit 'Enter' to continue...")
            test.report()
            #self.results.append(test.results)
    
    
    def __load_module(self):
        if os.path.exists(self.module_name+".py"):
            
            print("\033[44;1;4m-BEGIN OUTPUT FROM YOUR SCRIPT-\033[0m\033[44m")     
            exit_code = subprocess.call(["python", self.module_name+".py"])
            print("\033[1;4m-END OUTPUT FROM YOUR SCRIPT-\033[0m")     
            if exit_code == 0:
                oldstdout = sys.stdout
                sys.stdout = open("/dev/null", "w")
                try:
                    self.module = __builtins__['__import__'](self.module_name)
                except TypeError:
                    self.module = __builtins__.__import__(self.module_name)
                sys.stdout = oldstdout
                self.mod_src = inspect.getsource(self.module)
            else:
                print(errorMsg("Your script will has errors and cannot pass the tests."))
                exit(1)

        else:
            print errorMsg("Your script doesn't exist, is named incorrectly, or is in the wrong place")
            exit(2)

    def setUpResources(self):
        pass

    @property
    def requirements(self):
        return "\n".join([" \033[1;31m*\033[0m \033[33m{}\033[0m".format(t.__doc__) for t in self.tests if t.__name__.startswith('test_')]) 

    def run(self):
        self.results = []
        self.__load_module()
        self.setUpResources()
        for test in self.tests:
            test()


    def report(self):
        print(heading("DEATAILED RESULTS"))
        print(self.result_details)
        print(heading("RESULTS SUMMARY"))
        print(self.summary)

    @property
    def result_details(self):
        return "\n".join([str(r) for r in self.results])

    @property
    def summary(self):
        results = []
        for result in self.results:
            r = result.passed and PASSED or FAILED
            results.append("{}: {}".format(result.test_name, r))
        return "\n".join(results)
    
    @appendToResults
    def assertEqual(self, value1, value2, if_passed="Good Job!", if_failed=None):
        assertion_result = AssertionResult(actual=value1, target=value2)
        try:
            assert value1 == value2
            assertion_result.passed = True
            assertion_result.feedback = if_passed
        except AssertionError:
            assertion_result.passed = False
            assertion_result.feedback = if_failed
        return assertion_result

    @appendToResults
    def assertNotEqual(self, value1, value2, if_passed="Good Job!", if_failed=None):
        assertion_result = AssertionResult(actual=value1, target=value2)
        try:
            assert value1 != value2
            assertion_result.passed = True
            assertion_result.feedback = if_passed
        except AssertionError:
            assertion_result.passed = False
            assertion_result.feedback = if_failed
        return assertion_result

    @appendToResults
    def assertIsInstance(self, value1, _type, if_passed="Good Job!", if_failed=None):
        assertion_result = AssertionResult(actual=type(value1), target=_type)
        try:
            assert isinstance(value1, _type)
            assertion_result.passed = True
            assertion_result.feedback = if_passed
        except AssertionError:
            assertion_result.passed = False
            assertion_result.feedback = if_failed
        return assertion_result
            
        
        
class MyTests(Test):
    def __init__(self, module_name, *args, **kwargs):
        super(MyTests, self).__init__(module_name)
    
    def test_1_value_of_x(self):
        ''''x' should be 101'''
        value_for_x = 101
        self.assertEqual(self.module.x, 
                         value_for_x, 
                         if_failed="Your value for 'x' was {} not {}".format(self.module.x, value_for_x))

    def test_2_x_is_int(self):
        ''''x' should be <type 'int'>'''
        self.assertIsInstance(self.module.x, 
                              float, 
                              if_failed="'x' was a {}, not <type 'float'>".format(type(self.module.x)))

    def test_3_value_of_y(self):
        ''''y' should be 23'''
        value_for_y = 23
        self.assertEqual(self.module.y, 
                         value_for_y, 
                         if_failed="Your value for 'y' was {} not {}".format(self.module.y, value_for_y))

def main():
    Test.run_all()
if __name__=="__main__":
    MyTests('assignment1')
    main()
    
